 
/*  tm40.c
    Stephen D. Anderson --- October 29, 1994

    Top level module of HART user software.  

    Compile with Borland Turbo-C++ 1.0 compiler.  A batch file, tmbuild.sh, 
    is used to compile and link all modules and form an executable file, 
    tm40.exe.  Type "tmbuild" to create a new tm40.exe.

    tm40.c uses a window and menuing system called tcxl60.  It also uses 
    communication software called cport.  The tcxl60 and cport libraries 
    are linked by tmbuild.sh.

    tm40.c does the following in order:

    Initializes the screen and menuing system, saves existing screen, sets
    up its own full screen, display program title and copyright notice.

    Calls "getcom" so that user can specify the desired COM port.

    Turns ON DTR (data terminal ready) of the COM port and turns 
    OFF RTS (request to send).

    Disables floating point exceptions that would be fatal.

    Clears a global flag named "found".  This flag indicates whether 
    a field instrument has been found at the selected COM port.  

    Calls a function "getdrb" to ask the user whether to operate in 
    an analyzer mode.  In this mode all of the received bytes are 
    displayed in hex in a status window at the bottom of screen.

    Creates the status window.

    Goes to infinite while loop.  The first statement in the loop is a delay
    to prevent mouse selections in other modules from causing selections
    in this one.  This is followed by flushing of keyboard and mouse buffers,
    followed by presentation of the main menu.  The item selected from the
    menu is executed using switch-case.  Selecting the choice "Quit" causes 
    execution of function "Quit()", which breaks out of the while loop and 
    returns execution to the calling program.  Menu choices are part of two
    arrays that are established prior to "main".

    tm40.c contains the global variables related to the HART communication.


    ON ENTRY:   tm40 requires no arguments.

    ON EXIT:    returns value 0 to DOS or calling program.


    REVISION LOG:

    Version 4.3:
    1.  Changed startup message to show version 4.3.

*/

#include "tm40.h"

unsigned _stklen = 25000;   /* increase stack size. */

#include "global.h"     /*  global variables. */

    extern void selpv(void);    /*  select process variable. */
    extern void readpv(void);   /*  read process variable. */
    extern void getinfo(void);  /*  get information about field instrum. */
    extern void wrmsg(void);    /*  write a message to field instru. */
    extern void flash_led(void);    /*  flash the PRIMM or PRIMMPLEX LEDs. */
    extern void wpvcon(void);   /*  write process variable control. */
    extern void calib(void);    /*  calibrate field instrument. */
    extern void rprimv(void);   /*  read primary variable.  */
    extern void wrpoll(void);   /*  write polling address. */
    extern void wrtdd(void);    /*  write tag, descriptor, date to
                                field instrument. */
    extern void wreset(void);   /*  reset field instrument. */
    extern void userdef(void);  /*  user-defined function.  */
    extern void select(void);   /*  select polling address. */

    void menutitle(void);       /*  puts a title on the main menu. */
    void Quit(void);            /*  quits tm40.c */

    IbldT mainmenu[14] = 
    {
        {   "Flash LEDs",'A',ITM_BEF,1,2,20,0,0,
            NULL,NULL,flash_led,NULL,NULL
        },
        {   "Select Field Instrument",'B',ITM_BEF,2,2,0,0,0,
            0,0,select,0,0
        },
        {   "Get Field Instrument Characteristics",'C',ITM_BEF,
            3,2,0,0,0,NULL,NULL,getinfo,NULL,NULL
        },
        {   "Read Primary Variable",
            'D',ITM_BEF,4,2,0,0,0,NULL,NULL,rprimv,NULL,NULL
        },
        {   "Read Process Variables",'E',ITM_BEF,5,2,0,0,0,
            NULL,NULL,readpv,NULL,NULL
        },
        {   "Select Primary Variable",'F',ITM_BEF,6,2,0,0,0,
            NULL,NULL,selpv,NULL,NULL
        },
        {   "Write Primary Variable Control (range, damping)",'G',ITM_BEF,
            7,2,0,0,0,NULL,NULL,wpvcon,NULL,NULL
        },
        {   "Calibrate",'H',ITM_BEF,8,2,0,0,0,
            NULL,NULL,calib,NULL,NULL
        },
        {   "Write New Polling Address",
            'I',ITM_BEF,9,2,0,0,0,NULL,NULL,wrpoll,NULL,NULL
        },
        {   "Write Tag, Descriptor, Date to Field Instrument",
            'J',ITM_BEF,10,2,0,0,0,NULL,NULL,wrtdd,NULL,NULL
        },
        {   "Write Message to Field Instrument",'K',ITM_BEF,
            11,2,0,0,0,NULL,NULL,wrmsg,NULL,NULL
        },
        {   "Reset Field Instrument",'L',ITM_BEF,
            12,2,0,0,0,NULL,NULL,wreset,NULL,NULL
        },
        {   "User-Defined Command",'M',ITM_BEF,
            13,2,0,0,0,NULL,NULL,userdef,NULL,NULL
        },
        {   "Quit",'N',ITM_BEF,14,2,0,0,0,
            NULL,NULL,Quit,NULL,NULL
        }
    };

    MbldT menubuild =
    {   &mainmenu[0],
        14, 1,12,17,68, MNU_VER, BOX_SNG,
        WHITE|_BROWN,WHITE|_BROWN,WHITE|_BROWN,WHITE|_BROWN,
        WHITE|_BROWN,WHITE|_BLUE,20,menutitle,
        0,20,2,0xff,0xff,0xff
    };    

main()
{
    void getcom();
    void status_window_setup();
    void getdrb();
    void startmsg();

    TcxlInit();     /* start the windowing functions. */
    WsaveFull();    /* save existing screen. */
    WopenFull(WHITE|_LGREY);
    startmsg();     /* display startup message. */

    getcom();       /* get COM port. */

    ComDtr(com, 1);     /* turn on DTR in case of PRIMM. */
    ComRts(com, 0);     /* turn off RTS in case port initialize
                           didn't.  */

    /*  fix floating pt math so no abort of program.  */
    _control87(MCW_EM, EM_INVALID | EM_ZERODIVIDE | EM_OVERFLOW);

    found = 0;      /* indicate no field instrum. */

    getdrb();       /* display received bytes (netwk analyzer)? */

    /*  set up a status window at the bottom of the screen. */
    status_window_setup();


    while (1)   /* do forever. */
    {
        delay(500); /*  need this to avoid re-selecting from menu.  */
        KeyClear();
        MnuBuild(&menubuild); MnuGet();
    }
}

void Quit()

/*  
    Function is one of the possible user selections.  It causes the 
    execution of tm40.c to return to DOS.  Prior to this it closes any 
    open windows and closes the previously selected COM port.

    ON ENTRY:   Quit has no arguments.

    ON EXIT:    Quit returns execution to DOS.

    REVISION LOG:

*/

{
    WcloseAll();    /*  Close all windows.  */
    ComClose(com);  /*  close the serial port. */
    exit(0);
}

void menutitle()

/*  function menutitle displays a title for the main menu.

    ON ENTRY:   has no arguments.

    ON EXIT:    returns nothing.

    REVISION LOG:

*/

{
    Wtitle("<<  Select Desired Function  >>",TTL_CNT,WHITE|_BROWN);
}

void getdrb()

/*
    function getdrb asks whether to display received bytes in a crude
    network analyzer mode.  It gets a yes/no response.  ESC checking
    is turned OFF to force the user to make a choice.

    ON ENTRY:   has no arguments.

    ON EXIT:    the result of the yes/no query is effectively returned 
                by setting the global variable "drb" to 1 for yes, 0 for 
                no.  getdrb does not directly return anything.

    REVISION LOG:

*/

{
    extern byte drb;

    WsetEsc(OFF);    /*  don't allow escape.
                        force user to make selection.  */

    WpopUp(CNT_CNT,5,20,15,60,2,WHITE|_BLACK,WHITE|_BLACK);
    WputCen("\n\nDisplay Received Bytes? (y or n)");
    MnuAct(2);
    MnuItm(4,10,"Yes",'A',1,ITM_BEF,NULL,NULL,NULL);
    MnuItm(4,18,"No",'A',0,ITM_BEF,NULL,NULL,NULL);
    MnuEnd(1,MNU_HOR,5,1,WHITE|_BLACK,WHITE|_BLACK,
        WHITE|_BLACK,BLUE|_CYAN);
    drb = MnuGet();
    Wclose();
    WsetEsc(ON);
}

void getcom()

/*
    Function gets the COM port.  It finds the number of ports available 
    and presents a menu containing as many ports as it found.  The user 
    then selects the desired port.  The port is opened and the port 
    handle "com" (global variable) is set to one of COM1, COM2, etc.  
    COM1, COM2, etc. are #defines set up by cport.  The global variable 
    "comm" is also set to a value 1, 2, 3, or 4; corresponding to 
    COM1, COM2, COM3, or COM4.  "comm" tells other modules which port is 
    in use.

    The selected port is opened.  The port FIFO, if it exists, is 
    disabled.  This forces an interrupt on every received byte so that 
    errors associated with that byte can be stored as a separate byte.

    ON ENTRY:   has no arguments.

    ON EXIT:    getcom returns nothing directly, but sets the global 
                variables "com" and "comm".

    REVISION LOG:

*/

{
    extern COM com;
    extern byte comm;   /*  indicates which comm port (1,2,3,4) in use. */

    unsigned int ser[] = { COM1, COM2, COM3, COM4 };
    byte s[][5] = { "COM1", "COM2", "COM3", "COM4" };
    int i,j;

    WsetEsc(OFF);   /*  force user to make selection.  */

    /*  find out which ports are available. */
    j = HdwSer();

    /*  if lots of ports, truncate to 4.  */
    if (j > 4) j = 4;

    /*  build and call menu. */
    WpopUp(CNT_CNT,5,20,14,45,2,WHITE|_BLACK,WHITE|_BLACK);
    Wtitle("<<  Select Comm Port  >>",TTL_CNT,WHITE|_BLACK);
    MnuAct(j);
    for (i=0; i<j; ++i)
        MnuItm(1+i,0,s[i],'A',i,ITM_BEF,NULL,NULL,NULL);
    MnuEnd(0,MNU_VER,0,10,WHITE|_BLACK,WHITE|_BLACK,
        WHITE|_BLACK,BLUE|_CYAN);
    i = MnuGet(); comm = i+1;

    /*  open the port. */
    com = ComOpen(ser[i], B1200, W8|S1|ODD, 1024, 512);

    /*  force the UART to act like 8250. */
    if (ComUart(com) == NS16550) ComNS550(com, 0);

    Wclose();
    WsetEsc(ON);
}

void status_window_setup()

/*
    Function opens a window toward bottom of screen.  It labels the 
    window "status" and shows (1) which polling address is in use, (2) 
    which comm port in use, and (3) the number of communication tries for 
    the current communication.  A 2nd window is also used to display 
    received bytes in the logic analyzer mode if global variable "drb" is 
    set to 1.  The first window handle is returned as the global variable 
    "status_wind".  The 2nd window handle is returned as the global 
    variable "rec_byte_wind".

    ON ENTRY:   has no arguments.

    ON EXIT:    returns nothing.

    REVISION LOG

*/

{
    extern byte status_wind;
    extern byte comm;
    extern byte drb;

    status_wind = Wopen(19,0,21,79,5,BLACK|_GREEN,BLACK|_GREEN);
    Wprtns(0,0,BLACK|_GREEN," STATUS:",8);
    Wprtf(0,10,BLACK|_GREEN,"Com Port: COM%d   Com Try:",comm);
    Wprtf(0,42,BLACK|_GREEN,"Polling Addr in Use: ");
    if (drb)
    {
        Wprtns(2,1,BLACK|_CYAN,"RCV BYTES: ",12);
        rec_byte_wind = Wopen(22,0,24,79,5,BLACK|_CYAN,BLACK|_CYAN);
    }
}

void startmsg()

/*
    Function opens window and shows title, version number, and copyright 
    notice.  Displays for 3 seconds and then removes it and returns.

    ON ENTRY:   has no arguments.

    ON EXIT:    Returns nothing.

    REVISION LOG:

*/


{
    WpopUp(CNT_CNT,7,23,16,56,1,WHITE|_BLUE,WHITE|_BLUE);
    WputCen("\n\nPRIMM-WARE (tm) Version 4.3\n");
    WputCen("Copyright (c) 1995\n");
    WputCen("ANSCO, INC.");
    CurHide();  /* turn off cursor. */
    sleep(3);
    Wclose();
}

